using System;
using System.Collections.Generic;
using System.Text;
using Lexware.InhouseTools.TeamFoundation;
using System.Windows.Forms;
using Microsoft.TeamFoundation.WorkItemTracking.Client;

namespace WbsEditor
{
    public class WbsTfsClient
    {
        public WbsTfsClient()
        {
        }

        private TfsVersionControlClient _tfsClient;
        private TfsVersionControlClient TfsClient { get { return _tfsClient; } }
        private bool _isConnected;
        public bool IsConnected { get { return _isConnected; } }
        private string _teamProject;
        public string TeamProject { get { return _teamProject; } set { _teamProject = value; } }
        private string _iteration;
        public string Iteration { get { return _iteration; } set { _iteration = value; } }

        public event EventHandler SuccessfullyConnected;

        public string[] GetTeamProjectNames()
        {
            if (!IsConnected)
                return new string[] { };
            string[] teamProjects = TfsClient.EnumerateTeamProjects();
            return teamProjects;
        }

        public string[] GetTeamProjectIterations()
        {
            if (!IsConnected)
                return new string[] { };
            if (string.IsNullOrEmpty(TeamProject))
                return new string[] { };
            string[] iterations = TfsClient.EnumerateIterations(TeamProject);
            return iterations;
        }

        public string[] GetWorkItemTypes()
        {
            if (!IsConnected)
                return new string[] { };
            if (string.IsNullOrEmpty(TeamProject))
                return new string[] { };
            string[] wiTypes = TfsClient.EnumerateWorkItemTypes(TeamProject);
            return wiTypes;
        }

        public string[] GetFieldNames(string workItemType)
        {
            if (!IsConnected)
                return new string[] { };
            if (string.IsNullOrEmpty(TeamProject))
                return new string[] { };
            if (string.IsNullOrEmpty(workItemType))
                return new string[] { };
            string[] fieldNames = TfsClient.EnumerateFieldNames(TeamProject, workItemType);
            return fieldNames;
        }

        public void Connect(string url, bool useWindowsAuthentication,
            string userName, string domain, string password)
        {
            if (useWindowsAuthentication)
                _tfsClient = new TfsVersionControlClient(url);
            else
                _tfsClient = new TfsVersionControlClient(url, userName, domain, password);

            try
            {
                _tfsClient.Connect();
                _isConnected = true;
                SuccessfullyConnected(this, new EventArgs());
            }
            catch
            {
                _isConnected = false;
                throw;
            }
        }

        private string _lastProject = null;
        private bool _isDescriptionHtml = false;
        public bool IsDescriptionHtml()
        {
            if (_lastProject != TeamProject)
            {
                WorkItem wi = TfsClient.CreateNewTask(TeamProject);
                _isDescriptionHtml = wi.Fields["Description"].FieldDefinition.FieldType == FieldType.Html;
            }
            return _isDescriptionHtml;
        }

        public bool PublishWbsNode(WorkBreakdownStructure wbs, WbsNode node)
        {
            if (null == node)
                return false;
            if (null == wbs)
                return false;
            if (node.IsPublished)
                return true;
            if (!IsConnected)
            {
                MessageBox.Show("Not connected to Team Foundation Server. Can't publish work item.", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return false;
            }

            try
            {
                WorkItem workItem = TfsClient.CreateNewTask(TeamProject);
                workItem.Title = WbsExporter.BuildTitle(wbs, node);
                node.Wbs = wbs;
                workItem.Fields["Assigned To"].Value = node.AssignedTo;
                TrySetField(workItem, "Remaining Work", node.RemainingWorkInPersistUnits);
                TrySetField(workItem, "Completed Work", node.CompletedWorkInPersistUnits);
                workItem.Description = node.Description;
                if (string.IsNullOrEmpty(node.Iteration)
                    || (node.Iteration == "/"))
                {
                    if (WbsPlausiChecker.IsIterationPathValid(this, Iteration))
                    {
                        workItem.IterationPath = Iteration;
                    }
                }
                else
                {
                    if (WbsPlausiChecker.IsIterationPathValid(this, node.Iteration))
                    {
                        workItem.IterationPath = node.Iteration;
                    }
                    else if (WbsPlausiChecker.IsIterationPathValid(this, Iteration))
                    {
                        workItem.IterationPath = Iteration;
                    }
                }
                workItem.Fields["Priority"].Value = 1;
                if (!node.IsRoot && wbs.Root.IsPublished)
                {
                    TfsClient.SetWorkItemParentLink(workItem, wbs.Root.WorkItemId);
                }

                StringBuilder validation = new StringBuilder();
                foreach (Field field in workItem.Validate())
                {
                    if (!field.IsValid)
                    {
                        validation.AppendFormat("Invalid Field: {0} ({1})", field.Name, field.Status);
                        validation.AppendLine();
                    }
                }

                if (validation.Length > 0)
                    throw new Exception(validation.ToString());

                workItem.Save();
                node.WorkItemId = workItem.Id;
                node.State = workItem.State;
                node.Iteration = workItem.IterationPath;

                if (node.IsRoot)
                {
                    wbs.LastSaveDate = workItem.ChangedDate;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(String.Format("An error occurred while publishing the work item \"{0}\":\n\n{1}", node.Title, ex.Message), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            return true;
        }

        private static void TrySetField(WorkItem workItem, string fieldName, object val)
        {
            if (workItem.Fields.Contains(fieldName))
            {
                workItem.Fields[fieldName].Value = val;
            }
        }

        private static object TryGetField(WorkItem workItem, string fieldName)
        {
            if (workItem.Fields.Contains(fieldName))
            {
                return workItem.Fields[fieldName].Value;
            }
            return null;
        }

        private static float TryGetFloatField(WorkItem workItem, string fieldName)
        {
            object field = TryGetField(workItem, fieldName);
            if (null == field)
                return 0f;
            string fieldString = field.ToString();
            float result = 0f;
            if (float.TryParse(fieldString, out result))
                return result;
            return 0f;
        }

        public delegate void PublishFunction<WorkBreakdownStructure, WbsNode, WorkItem>(WorkBreakdownStructure wbs, WbsNode node, WorkItem workItem);

        public bool PublishNodeWithDelegate(WorkBreakdownStructure wbs, WbsNode node, PublishFunction<WorkBreakdownStructure, WbsNode, WorkItem> publishFunction)
        {
            if (null == node)
                return false;
            if (!node.IsPublished)
                return false;

            try
            {
                WorkItem workItem = TfsClient.GetWorkItemById(node.WorkItemId);
                if (null == workItem)
                    return false;
                if (HasDifferences(wbs, workItem, node, !node.IsRoot))
                {
                    publishFunction(wbs, node, workItem);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("An error occurred while updating the data of Work Item #" + node.WorkItemId + "\n\n:" + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            return true;
        }

        public bool PublishAllFieldsExceptDescription(WorkBreakdownStructure wbsParam, WbsNode nodeParam)
        {
            return PublishNodeWithDelegate(wbsParam, nodeParam,
                delegate(WorkBreakdownStructure wbs, WbsNode node, WorkItem workItem)
                {
                    node.Wbs = wbs;
                    workItem.Title = WbsExporter.BuildTitle(wbs, node);
                    workItem.State = node.State;
                    workItem.Fields["Assigned To"].Value = node.AssignedTo;
                    TrySetField(workItem, "Remaining Work", node.RemainingWorkInPersistUnits);
                    TrySetField(workItem, "Completed Work", node.CompletedWorkInPersistUnits);
                    if (!string.IsNullOrEmpty(node.Iteration))
                        workItem.IterationPath = node.Iteration;
                    workItem.Fields["Priority"].Value = node.Priority;
                    workItem.Save();
                });
        }

        public bool PublishAllFields(WorkBreakdownStructure wbsParam, WbsNode nodeParam)
        {
            return PublishNodeWithDelegate(wbsParam, nodeParam,
                delegate(WorkBreakdownStructure wbs, WbsNode node, WorkItem workItem)
                {
                    node.Wbs = wbs;
                    workItem.Title = WbsExporter.BuildTitle(wbs, node);
                    workItem.State = node.State;
                    workItem.Fields["Assigned To"].Value = node.AssignedTo;
                    TrySetField(workItem, "Remaining Work", node.RemainingWorkInPersistUnits);
                    TrySetField(workItem, "Completed Work", node.CompletedWorkInPersistUnits);
                    if (!string.IsNullOrEmpty(node.Iteration))
                        workItem.IterationPath = node.Iteration;
                    workItem.Fields["Priority"].Value = node.Priority;
                    workItem.Description = node.Description;
                    workItem.Save();
                });
        }

        private bool HasDifferences(WorkBreakdownStructure wbs, WorkItem workItem, WbsNode node, bool checkDescription)
        {
            string title = WbsExporter.BuildTitle(wbs, node);
            if (workItem.Title != title)
                return true;
            if (workItem.State != node.State)
                return true;
            float remainingWork = TryGetFloatField(workItem, "Remaining Work");
            float completedWork = TryGetFloatField(workItem, "Completed Work");
            if (Math.Abs(node.RemainingWorkInPersistUnits - remainingWork) > 0.004f)
                return true;
            if (Math.Abs(node.CompletedWorkInPersistUnits - completedWork) > 0.004f)
                return true;
            if (workItem.IterationPath != node.Iteration)
                return true;
            if (checkDescription &&
                workItem.Description != node.Description)
                return true;
            if (((int)workItem.Fields["Priority"].Value) != node.Priority)
                return true;
            if (((string)workItem.Fields["Assigned To"].Value) != node.AssignedTo)
                return true;
            return false;
        }

        public bool PublishTitle(WorkBreakdownStructure wbsParam, WbsNode nodeParam)
        {
            return PublishNodeWithDelegate(wbsParam, nodeParam,
                delegate(WorkBreakdownStructure wbs, WbsNode node, WorkItem workItem)
                {
                    string title = node.Title;
                    string newTitle = WbsExporter.BuildTitle(wbs, node);
                    if (newTitle.CompareTo(workItem.Title) != 0)
                    {
                        RefreshNodeFromWorkItem(wbs, node, workItem);
                        workItem.Title = newTitle;
                        node.Title = title;
                        workItem.Save();
                    }
                });
        }

        public void OpenWbsNode(WorkBreakdownStructure wbs, WbsNode node)
        {
            if (null == node)
                return;
            int workItemId = node.WorkItemId;
            if (workItemId <= 0)
                return;
            if (!IsConnected)
            {
                MessageBox.Show("Not connected to Team Foundation Server. Can't open work item.", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            try
            {
                WorkItem workItem = TfsClient.GetWorkItemById(workItemId);
                int id = TfsClient.DisplayWorkItemDialog(workItem);
                if (id != 0)
                {
                    // Item was saved
                    RefreshNodeFromWorkItem(wbs, node, workItem);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("An error occurred while opening and refreshing the work item:\n\n" + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        public void RefreshWbs(WorkBreakdownStructure wbs)
        {
            RefreshWbsNodeRecursive(wbs, wbs.Root);
        }

        private void RefreshWbsNodeRecursive(WorkBreakdownStructure wbs, WbsNode node)
        {
            if (node.IsPublished && !node.IsRoot)
            {
                RefreshWbsNode(wbs, node);
            }

            foreach (WbsNode child in node.ChildNodes)
            {
                RefreshWbsNodeRecursive(wbs, child);
            }
        }

        public void RefreshWbsNode(WorkBreakdownStructure wbs, WbsNode node)
        {
            if (null == node)
                return;
            int workItemId = node.WorkItemId;
            if (workItemId <= 0)
                return;
            if (!IsConnected)
            {
                MessageBox.Show("Not connected to Team Foundation Server. Can't refresh work item.", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            try
            {
                WorkItem workItem = TfsClient.GetWorkItemById(workItemId);
                if (null == workItem)
                    return;
                RefreshNodeFromWorkItem(wbs, node, workItem);
            }
            catch (Exception ex)
            {
                MessageBox.Show("An error occurred while refreshing the work item:\n\n" + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void RefreshNodeFromWorkItem(WorkBreakdownStructure wbs, WbsNode node, WorkItem workItem)
        {
            node.Title = WbsExporter.ExtractTitle(workItem.Title, node.IsRoot);
            node.AssignedTo = workItem.Fields["Assigned To"].Value.ToString();
            node.State = workItem.State;
            node.PossibleNextStates = TfsClient.GetPossibleNextStates(workItem.Project.Name, "Task", workItem.State);
            node.Iteration = workItem.IterationPath;
            if (!node.IsRoot)
            {
                float remainingWork = 0f;
                float completedWork = 0f;
                if (workItem.Fields.Contains("Remaining Work")
                    && null != workItem.Fields["Remaining Work"].Value)
                {
                    if (!float.TryParse(workItem.Fields["Remaining Work"].Value.ToString(), out remainingWork))
                        remainingWork = 0f;
                }
                if (workItem.Fields.Contains("Completed Work")
                    && null != workItem.Fields["Completed Work"].Value)
                {
                    if (!float.TryParse(workItem.Fields["Completed Work"].Value.ToString(), out completedWork))
                        completedWork = 0f;
                }
                node.Wbs = wbs;
                node.RemainingWorkInPersistUnits = remainingWork;
                node.CompletedWorkInPersistUnits = completedWork;
                node.Description = workItem.Description;
                int priority;
                if (int.TryParse(workItem.Fields["Priority"].Value.ToString(), out priority))
                    node.Priority = priority;
            }

            WbsPlausiChecker.CheckNode(wbs, node, this);
        }

        public string[] GetPossibleAssignedToNames()
        {
            if (!IsConnected || string.IsNullOrEmpty(TeamProject))
                return new string[] { };

            try
            {
                WorkItem workItem = TfsClient.CreateNewTask(TeamProject);
                List<string> names = new List<string>();
                foreach (object name in workItem.Fields["Assigned To"].AllowedValues)
                {
                    names.Add(name.ToString());
                }
                names.Sort();
                return names.ToArray();
            }
            catch (Exception)
            {
                // ...
            }
            return new string[] { };
        }

        public bool AttachNodeToWorkItem(WorkBreakdownStructure wbs, WbsNode node, int workItemId)
        {
            if (node == null)
                return false;
            if (!IsConnected)
            {
                MessageBox.Show("Can't attach to Work Item without being connected to Team System.", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return false;
            }
            try
            {
                WorkItem workItem = TfsClient.GetWorkItemById(workItemId);
                if (null == workItem)
                {
                    MessageBox.Show("Could not retrieve information on Work Item with Id #" + workItemId, "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return false;
                }
                node.WorkItemId = workItem.Id;
                RefreshNodeFromWorkItem(wbs, node, workItem);
            }
            catch (Exception ex)
            {
                MessageBox.Show("An error occurred while attaching the selected node to Work Item #" + workItemId + "\n\n" + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            return true;
        }

        public bool PublishDescription(WorkBreakdownStructure wbs, WbsNode node, ref DateTime saveDate)
        {
            if (null == node)
                return false;
            if (!IsConnected)
                return false;
            if (!node.IsPublished)
                return false;
            try
            {
                WorkItem workItem = TfsClient.GetWorkItemById(node.WorkItemId);
                if (null == workItem)
                {
                    MessageBox.Show("Could not retrieve work item #" + node.WorkItemId + " for Description publishing.", "Error",
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return false;
                }
                if (!XmlMatch(workItem.Description, node.Description))
                {
                    workItem.Description = node.Description;
                    if (node.IsRoot)
                    {
                        node.Wbs = wbs;
                        TrySetField(workItem, "Remaining Work", node.RemainingWorkInPersistUnits);
                        TrySetField(workItem, "Completed Work", node.CompletedWorkInPersistUnits);
                    }
                    workItem.Save();
                }
                saveDate = workItem.ChangedDate;
                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show("While storing the description of work item #" + node.WorkItemId + ", the following error occurred:\n\n" + ex.Message, "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            return false;
        }

        public bool XmlMatch(string one, string two)
        {
            if ((one == null) && (two == null)) return true;
            if ((one == null) && (two != null)) return false;
            if ((one != null) && (two == null)) return false;

            string one2 = one.Replace("\r", "").Replace("\n", "");
            string two2 = two.Replace("\r", "").Replace("\n", "");
            return (one2 == two2);
        }

        public string GetWorkItemDescription(int rootId)
        {
            if (rootId <= 0)
                return null;
            if (!IsConnected)
                return null;
            try
            {
                WorkItem workItem = TfsClient.GetWorkItemById(rootId);
                if (null == workItem)
                {
                    MessageBox.Show("Could not retrieve the WBS definition from work item #" + rootId + ".\n\n" +
                        "Does the given work item exist, and do you have the permission to read it?", "Information",
                        MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return null;
                }

                return workItem.Description;
            }
            catch (Exception ex)
            {
                MessageBox.Show("While fetching the WBS definition from work item #" + rootId + ",\nthe following error occurred:\n\n" + ex.Message, "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            return null;
        }

        public WorkItemCollection GetWorkItemsFromQuery(string query)
        {
            return TfsClient.GetWorkItemsFromQuery(query);
        }

        public void ResetNodeValues(WbsNode node)
        {
            try
            {
                WorkItem workItem = TfsClient.GetWorkItemById(node.WorkItemId);
                if (null == workItem)
                {
                    MessageBox.Show("Could not retrieve the WBS definition from work item #" + node.WorkItemId + ".\n\n" +
                        "Does the given work item exist, and do you have the permission to read it?", "Information",
                        MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return;
                }

                workItem.Title = "(Deleted)";
                workItem.Fields["Assigned To"].Value = "";
                TrySetField(workItem, "Remaining Work", 0f);
                workItem.State = "Closed";
                workItem.Reason = "Obsolete";
                workItem.Save();
            }
            catch (Exception ex)
            {
                MessageBox.Show("While resetting the values for 'Assigned To' and 'Remaining Work', the following error occurred:\n\n" + ex.Message,
                    "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        public bool CheckLastSave(WorkBreakdownStructure wbs)
        {
            if (!wbs.Root.IsPublished)
                return true;

            WorkItem workItem = TfsClient.GetWorkItemById(wbs.Root.WorkItemId);
            if (null == workItem)
                return true;

            if (workItem.ChangedDate > wbs.LastSaveDate)
            {
                string msg = string.Format("The Work Item #{0} has been changed by someone else since the last save.\n\n" +
                    "Changed Date: {1}, {2}\n" +
                    "Changed By: {3}\n\n" +
                    "Do you still want to save? If you save, you might overwrite changes made by someone else.",
                    workItem.Id,
                    workItem.ChangedDate.ToLongDateString(),
                    workItem.ChangedDate.ToLongTimeString(),
                    workItem.ChangedBy);

                DialogResult res = MessageBox.Show(msg, "Warning", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning);

                if (DialogResult.Yes == res)
                    return true;
                return false;
            }
            return true;
        }

        public void SetWorkItemLinks(WorkBreakdownStructure wbs)
        {
            WbsTraverser.TraverseTree(wbs.Root, delegate(WbsNode node)
                {
                    if (node.IsPublished && !node.IsRoot)
                    {
                        WorkItem workItem = TfsClient.GetWorkItemById(node.WorkItemId);
                        TfsClient.SetWorkItemParentLink(workItem, wbs.Root.WorkItemId);
                        workItem.Save();
                    }
                });
        }
    }
}
